#region Using Directives

using System;
using System.IO;
using System.Windows.Forms;
using System.Globalization;
using Microsoft.Practices.ComponentModel;
using Microsoft.Practices.RecipeFramework;
using Microsoft.Practices.RecipeFramework.Library;
using Microsoft.Practices.RecipeFramework.Services;
using Microsoft.Practices.RecipeFramework.VisualStudio;
using Microsoft.Practices.RecipeFramework.VisualStudio.Templates;
using EnvDTE;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Text.RegularExpressions;
using EnvDTE80;
using Microsoft.VisualStudio;
using Microsoft.Win32;
using Microsoft.VisualStudio.Shell;
using Microsoft.Practices.Common.Services;
using System.Collections.Generic;
using Microsoft.Practices.RecipeFramework.VisualStudio.Library.Templates;
using System.CodeDom.Compiler;
using System.Text;
using System.Xml;

#endregion

namespace SPALM.SharePointSoftwareFactory.Library.Actions
{
    /// <summary>
    /// Renames a project
    /// </summary>
    [ServiceDependency(typeof(DTE))]
    public class MigrateSelectedProjects : ConfigurableAction
    {
        public override void Execute()
        {
            DTE service = (DTE)this.GetService(typeof(DTE));
            
            try
            {
                Project selectedProject = Helpers.GetSelectedProject(service);
                if (selectedProject != null)
                {
                    Helpers.LogMessage(service, this, "*** Migrating selected projects ***");
                    MigrateProject(service, selectedProject);
                }
                else
                {
                    Helpers.LogMessage(service, this, "*** Migrating contained projects ***");
                    foreach (Project project in Helpers.GetAllProjects(service))
                    {
                        MigrateProject(service, project);
                    } 
                }                              
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

        private void MigrateProject(DTE service, Project project)
        {
            Helpers.LogMessage(service, this, "Migrating project '" + project.Name + "'");
            try
            {
                if (Helpers.IsCustomizationProject(project))
                {
                    MigrateCustomizationProject(service, project);
                }
                else if (project.Name == "ApplicationDeployment")
                {
                    MigrateProjectApplicationDeployment(project);
                }
            }
            catch (Exception ex)
            {
                Helpers.LogMessage(service, this, "Error: Migration of project '" + project.Name + "' failed: " + ex.Message);
            }
        }

        private void MigrateCustomizationProject(DTE service, Project project)
        {
            Helpers.EnsureCheckout(service, project);

            Helpers.LogMessage(service, this, "*** Migrating project '" + project.Name + "' ****");

            if (Helpers2.IsVSTemplate(service, project))
            {
              try
              {
                ProjectItem packageGeneratorTT = Helpers.GetProjectItemByName(Helpers.GetProjectItemByName(project.ProjectItems, "Package").ProjectItems, "Package.Generator.tt");
                if (packageGeneratorTT != null)
                {
                  Helpers.LogMessage(service, this, "Deleting Package/Package.Generator.tt");
                  Helpers.EnsureCheckout(service, packageGeneratorTT);
                  packageGeneratorTT.Delete();
                }
              }
              catch
              {
                Helpers.LogMessage(service, this, "Warning: Could not delete Package/Package.Generator.tt");
              }

              try
              {
                ProjectItem packageGeneratorTMP = Helpers.GetProjectItemByName(Helpers.GetProjectItemByName(project.ProjectItems, "Package").ProjectItems, "Package.Generator.tmp");
                if (packageGeneratorTMP != null)
                {
                  Helpers.LogMessage(service, this, "Deleting Package/Package.Generator.tmp");
                  Helpers.EnsureCheckout(service, packageGeneratorTMP);
                  packageGeneratorTMP.Delete();
                }
              }
              catch
              {
                Helpers.LogMessage(service, this, "Warning: Could not delete Package/Package.Generator.tmp");
              }                            

              Helpers.LogMessage(service, this, "Settings IncludeAssemblyInPackge to True");
              try
              {
                Helpers.SetProjectPropertyGroupValue(project, "IncludeAssemblyInPackage", "True");
              }
              catch
              { 
              }
            }
            else
            {
                //!!! only for HIVETemplate!!!, different migration for VSEWSSTemplate
                Rename12ToSharePointRoot(project);

                //1. Add manifest.tt
                AddManifestTT(project);

                //2. Add makecab.tt
                AddMakeCabTT(project);

                //3. replace <#= GetManifest() #> with <#= GetManifest("12") #> in manifest.tt
                string manifestTT = Path.Combine(Helpers.GetProjectFolder(project), "manifest.tt");
                if (File.Exists(manifestTT))
                {
                    string existingcode = File.ReadAllText(manifestTT);
                    if (existingcode.Contains("<#= GetManifest() #>"))
                    {
                      Helpers.EnsureCheckout(service, manifestTT);
                        existingcode.Replace("<#= GetManifest() #>", "<#= GetManifest(\"" + Helpers.GetSharePointVersion(project.DTE) + "\") #>");
                        File.WriteAllText(manifestTT, existingcode);
                    }
                }

                //3. add property SPSFDeploymentTarget if property is not available
                try
                {
                  string testvalue = Helpers.GetProjectPropertyGroupValue(project, "SPSFDeploymentTarget", "NOTFOUND").ToString();
                  if (testvalue == "NOTFOUND")
                  {
                    Helpers.LogMessage(service, this, "Updated project property 'SPSFDeploymentTarget'");
                    Helpers.SetProjectPropertyGroupValue(project, "SPSFDeploymentTarget", "GlobalAssemblyCache");
                  }
                }
                catch { }
            }

            //4. add property Sandboxedsolution if property is not available
            try
            {
                string testvalue = Helpers.GetProjectPropertyGroupValue(project, "Sandboxedsolution", "NOTFOUND").ToString();
                if (testvalue == "NOTFOUND")
                {
                    Helpers.LogMessage(service, this, "Updated project property 'Sandboxedsolution'");
                    Helpers.SetProjectPropertyGroupValue(project, "Sandboxedsolution", "False");
                }
            }
            catch { }

            try
            {
                string nameOfProject =  project.Name;
                Helpers2.MoveProjectToSolutionFolder(service, project.Name, "Solutions");
               //after moving we need to get the moved project again from the name
                project = Helpers.GetProjectByName(service, nameOfProject);
            }
            catch { }



            //5. Add import to SharePoint.targets (e.g. for WSP projects)
            string fileName = project.FullName;
            Helpers.SelectProject(project);

            if (service.SuppressUI || MessageBox.Show("The project file of project " + project.Name + " must be updated. Can SPSF save and unload the project?", "Unloading project", MessageBoxButtons.YesNo) == DialogResult.Yes)
            {
                service.Documents.CloseAll(vsSaveChanges.vsSaveChangesPrompt);

                try
                {
                    Helpers.LogMessage(project.DTE, this, "Updating csproj file");
                    service.ExecuteCommand("File.SaveAll", string.Empty);
                    service.ExecuteCommand("Project.UnloadProject", string.Empty);
                    MigrateFile(fileName);
                    //project.Imports.AddNewImport(@"C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.CSharp.targets", null);
                    //project.Save(fileName);
                    service.ExecuteCommand("Project.ReloadProject", string.Empty);
                }
                catch { }
            }      

        }

        private void AddMakeCabTT(Project project)
        {
            Helpers.LogMessage(project.DTE, this, "Adding makecab.tt");

            //warning: Name of WSP file is here name of assembly. This may not be conform to naming conventions of spsf
            string WSPFilename = project.Properties.Item("AssemblyName").Value.ToString(); // Helpers.GetApplicationName(project.DTE);
            
            Dictionary<string, PropertyData> arguments = new Dictionary<string, PropertyData>();
            arguments.Add("WSPFileName", new PropertyData(WSPFilename, typeof(System.String)));            

            CheckTTFile(project, "makecab", ".ddf", @"Text\makecab.tt.t4", arguments);           
        }

        private void AddManifestTT(Project project)
        {
            Helpers.LogMessage(project.DTE, this, "Adding manifest.tt");

            string WSPSolutionID = "";
            string solutionIdFile = Path.Combine(Helpers.GetProjectFolder(project), "solutionid.txt");
            if (File.Exists(solutionIdFile))
            {
                WSPSolutionID = File.ReadAllText(solutionIdFile);
                try
                {
                    WSPSolutionID = new Guid(WSPSolutionID).ToString();
                }
                catch { }
            }

            if (string.IsNullOrEmpty(WSPSolutionID))
            {
                WSPSolutionID = new Guid().ToString();
            }
            if (!WSPSolutionID.StartsWith("{"))
            {
                WSPSolutionID = "{" + WSPSolutionID + "}";
            }

              //ups, no spsf project, so we add manifest.tt
            Dictionary<string, PropertyData> arguments = new Dictionary<string, PropertyData>();
            arguments.Add("WSPSolutionID", new PropertyData(WSPSolutionID, typeof(System.String)));
            arguments.Add("WSPDeploymentServerType", new PropertyData("WebFrontEnd", typeof(System.String)));
            arguments.Add("WSPResetServer", new PropertyData(false, typeof(System.Boolean)));
            arguments.Add("IsSandBoxedSolution", new PropertyData(Helpers.GetIsSandboxedSolution(project), typeof(System.Boolean)));
            arguments.Add("SharePointVersion", new PropertyData(Helpers.GetSharePointVersion(project.DTE), typeof(System.String)));

            CheckTTFile(project, "manifest", ".tt", @"Text\manifest.tt.t4", arguments);
        }

        private void CheckTTFile(Project project, string filenameWithoutExtension, string extension, string templateFilename, Dictionary<string, PropertyData> arguments)
        {
            //add manifest.tt 
            bool manifestXmlFound = false;
            bool manifestTTFound = false;

            string manifestXml = Path.Combine(Helpers.GetProjectFolder(project), filenameWithoutExtension + extension);
            if (File.Exists(manifestXml))
            {
                manifestXmlFound = true;
            }
            string manifestTT = Path.Combine(Helpers.GetProjectFolder(project), filenameWithoutExtension + ".tt");
            if (File.Exists(manifestTT))
            {
                manifestTTFound = true;
            }

            //1. if manifest.xml exists in root but no manifest.tt then we have spsf but AutoCreateManifest is false so we do nothing
            if (manifestXmlFound && !manifestTTFound)
            {
                //no manifest.tt but manifest.xml -> user wants to edit mainfest.xml themselves
                return;
            }

            //3. if manifest.xml and manifest.tt is available we do nothing because in spsf 3.0 we have no changes
            if (manifestXmlFound && manifestTTFound)
            {
                //everything uptodate
                return;
            }

            //2. if manifest.xml is not available in root, then we have wspbuilder and we ADD manifest.tt new, but we need to find the SolutionId first
            if (!manifestXmlFound)
            {
                string manifestTTCode = TransformTemplate(templateFilename, arguments);                
                string tempFileName = Path.GetTempFileName();
                File.WriteAllText(tempFileName, manifestTTCode);
                Helpers.AddFromTemplate(project.ProjectItems, tempFileName, filenameWithoutExtension + ".tt");
                File.Delete(tempFileName);                
            }            
        }        

        private string TransformTemplate(string templateFile,  Dictionary<string, PropertyData> arguments)
        {
            EnvDTE.DTE vs = this.GetService<EnvDTE.DTE>(true);
            string basePath = this.GetBasePath();

            string templateBasePath = this.GetTemplateBasePath();
            if (!Path.IsPathRooted(templateFile))
            {
                templateFile = Path.Combine(templateBasePath, templateFile);
            }
            templateFile = new FileInfo(templateFile).FullName;
            if (!templateFile.StartsWith(templateBasePath))
            {
                throw new ArgumentException("Starts not with " + templateBasePath);
            }
            string templateCode = File.ReadAllText(templateFile);


            Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngine engine = new Microsoft.VisualStudio.TextTemplating.Engine();
            IValueInfoService service = (IValueInfoService)this.GetService(typeof(IValueInfoService));

            TemplateHost host = new TemplateHost(basePath, arguments);
            host.TemplateFile = templateFile;
            string str3 = engine.ProcessTemplate(templateCode, host);
            if (host.Errors.HasErrors)
            {
                string errors = "";
                foreach (CompilerError error in host.Errors)
                {
                    Helpers.LogMessage(vs, this, error.ErrorText);
                    errors += error.ErrorText + Environment.NewLine;
                }
                throw new TemplateException(host.Errors);
            }
            if (host.Errors.HasWarnings)
            {
                StringBuilder builder = new StringBuilder();
                foreach (CompilerError error in host.Errors)
                {
                    builder.AppendLine(error.ErrorText);
                }
                //Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "CompilationWarnings", new object[] { templateFile, builder.ToString() }));
            }
            return str3;
        }

        protected string GetBasePath()
        {
            return base.GetService<IConfigurationService>(true).BasePath;
        }

        protected string GetTemplateBasePath()
        {
            return new DirectoryInfo(this.GetBasePath() + @"\Templates").FullName;
        }

        private void MigrateProjectApplicationDeployment(Project project)
        {
            Helpers.LogMessage((DTE)this.GetService(typeof(DTE)), this, "*** Migrating project '" + project.Name + "' ****");
            Helpers.EnsureCheckout(project.DTE, project);

            string templateBasePath = this.GetTemplateBasePath();

            //replace deployment.msbuild
            string sPVersion = Helpers.GetSharePointVersion(project.DTE);
            Helpers.AddFromTemplate(project.ProjectItems, Path.Combine(templateBasePath, @"Solutions\Projects\Deploy\Deployment.msbuild"), "Deployment.msbuild", true);
            
            //replace deployment.msbuild
            Helpers.AddFromTemplate(project.ProjectItems, Path.Combine(templateBasePath, @"Solutions\Projects\Deploy\ParametersUserInput.msbuild"), "ParametersUserInput.msbuild", true);

            //replace msbuild.dll
            ProjectItem msbuildFolder = Helpers.GetProjectFolder(project.ProjectItems, "MSBuild", true);
            Helpers.AddFromTemplate(msbuildFolder.ProjectItems, Path.Combine(templateBasePath, @"Solutions\Projects\Deploy\MSBuild\SPALM.MSBuildTasks.dll"), "SPALM.MSBuildTasks.dll", true);
            ProjectItem oldMsbuildFile = Helpers.GetProjectItemByName(msbuildFolder.ProjectItems, "MSBuild.Extensions.SharePoint.dll");
            if (oldMsbuildFile != null)
            {
                oldMsbuildFile.Delete();
            }

            //batch files
            ProjectItem deploymentFilesFolder = Helpers.GetProjectFolder(project.ProjectItems, "DeploymentFiles", true);
            Helpers.AddFromTemplate(deploymentFilesFolder.ProjectItems, Path.Combine(templateBasePath, @"Solutions\Projects\Deploy\DF\StartDeploy.bat"), "StartDeploy.bat", true);
            Helpers.AddFromTemplate(deploymentFilesFolder.ProjectItems, Path.Combine(templateBasePath, @"Solutions\Projects\Deploy\DF\StartRedeploy.bat"), "StartRedeploy.bat", true);
            Helpers.AddFromTemplate(deploymentFilesFolder.ProjectItems, Path.Combine(templateBasePath, @"Solutions\Projects\Deploy\DF\StartUndeploy.bat"), "StartUndeploy.bat", true);
            Helpers.AddFromTemplate(deploymentFilesFolder.ProjectItems, Path.Combine(templateBasePath, @"Solutions\Projects\Deploy\DF\StartUpgrade.bat"), "StartUpgrade.bat", true);
        
        }

        private void Rename12ToSharePointRoot(Project project)
        {
            ProjectItem hiveItem = null;
            try
            {
                hiveItem = Helpers.GetProjectItemByName(project.ProjectItems, "12");
                if (hiveItem != null)
                {
                    hiveItem.Name = "SharePointRoot";
                    Helpers.LogMessage(project.DTE, this, "Renamed folder '12' to 'SharePointRoot'");
                    return;
                }
            }
            catch { }

            try
            {
                hiveItem = Helpers.GetProjectItemByName(project.ProjectItems, "14");
                if (hiveItem != null)
                {
                    hiveItem.Name = "SharePointRoot";
                    Helpers.LogMessage(project.DTE, this, "Renamed folder '14' to 'SharePointRoot'");
                    return;
                }
            }
            catch { }

            Helpers.LogMessage(project.DTE, this, "Warning: Could not rename folder 12 or 14 to 'SharePointRoot'");
        }






        private void MigrateFile(string path)
        {
          try
          {
            File.Copy(path, path + ".bak", true);
          }
          catch {}

            string removedContent = "";

            XmlDocument csprojfile = new XmlDocument();
            csprojfile.Load(path);

            XmlNamespaceManager newnsmgr = new XmlNamespaceManager(csprojfile.NameTable);
            newnsmgr.AddNamespace("ns", "http://schemas.microsoft.com/developer/msbuild/2003");

            XmlNode nodeProject = csprojfile.SelectSingleNode("/ns:Project", newnsmgr);

            //check for nodeimport

            CheckImportNode(csprojfile, nodeProject, @"$(SolutionDir)\SharePointTargets.targets", @"!Exists('$(MSBuildProjectDirectory)\..\SharePointTargets.targets')");
            CheckImportNode(csprojfile, nodeProject, @"$(MSBuildProjectDirectory)\..\SharePointTargets.targets", @"Exists('$(MSBuildProjectDirectory)\..\SharePointTargets.targets')");

            /*
             <Import Condition="!Exists('$(MSBuildProjectDirectory)\..\SharePointTargets.targets')" Project="$(SolutionDir)\SharePointTargets.targets" />
             <Import Condition=" Exists('$(MSBuildProjectDirectory)\..\SharePointTargets.targets')" Project="$(MSBuildProjectDirectory)\..\SharePointTargets.targets" />
    
             */


            XmlNode nodeBeforeBuild = csprojfile.SelectSingleNode("/ns:Project/ns:Target[@Name='BeforeBuild' and @DependsOnTargets='$(BeforeBuildDependsOn)']", newnsmgr);
            if (nodeBeforeBuild == null)
            {
                XmlNode nodeBeforeBuildOther = csprojfile.SelectSingleNode("/ns:Project/ns:Target[@Name='BeforeBuild']", newnsmgr);
                if (nodeBeforeBuildOther != null)
                {   //remove this node with same name
                    removedContent += nodeBeforeBuildOther.OuterXml + Environment.NewLine;
                    nodeProject.RemoveChild(nodeBeforeBuildOther);
                }
                AddBeforeBuildNode(csprojfile, nodeProject);
            }

            XmlNode nodeAfterBuild = csprojfile.SelectSingleNode("/ns:Project/ns:Target[@Name='AfterBuild' and @DependsOnTargets='$(AfterBuildDependsOn)']", newnsmgr);
            if (nodeAfterBuild == null)
            {
                XmlNode nodeAfterBuildOther = csprojfile.SelectSingleNode("/ns:Project/ns:Target[@Name='AfterBuild']", newnsmgr);
                if (nodeAfterBuildOther != null)
                {   //remove this node with same name
                    removedContent += nodeAfterBuildOther.OuterXml + Environment.NewLine;
                    nodeProject.RemoveChild(nodeAfterBuildOther);
                }
                AddAfterBuildNode(csprojfile, nodeProject);
            }

            if (removedContent != "")
            {
                XmlComment comment = csprojfile.CreateComment("Following content has been removed during migration with SPSF" + Environment.NewLine + removedContent);
                nodeProject.AppendChild(comment);
            }
                      
            csprojfile.Save(path);
        }

        private void CheckImportNode(XmlDocument csprojfile, XmlNode nodeProject, string projectString, string conditionString)
        {
            Helpers.LogMessage((DTE)this.GetService(typeof(DTE)), this, "Adding new Import node");

            XmlNamespaceManager newnsmgr = new XmlNamespaceManager(csprojfile.NameTable);
            newnsmgr.AddNamespace("ns", "http://schemas.microsoft.com/developer/msbuild/2003");
            XmlNode nodeImport = csprojfile.SelectSingleNode("/ns:Project/ns:Import[@Project='" + projectString + "']", newnsmgr);
            if (nodeImport == null)
            {
                //ok, die 1. node ist noch nicht da
                XmlElement importNode = csprojfile.CreateElement("Import", "http://schemas.microsoft.com/developer/msbuild/2003");
                nodeProject.AppendChild(importNode);
                XmlAttribute importAttribute = csprojfile.CreateAttribute("Project"); //, "http://schemas.microsoft.com/developer/msbuild/2003");
                importAttribute.Value = projectString;
                importNode.Attributes.Append(importAttribute);
                XmlAttribute condiAttribute = csprojfile.CreateAttribute("Condition"); //, "http://schemas.microsoft.com/developer/msbuild/2003");
                condiAttribute.Value = conditionString;
                importNode.Attributes.Append(condiAttribute);
            }
            else
            {
                //ok, die node ist da, ist aber auch die condition richtig?
                if ((nodeImport.Attributes["Condition"] != null) && (nodeImport.Attributes["Condition"].Value.Trim() == conditionString))
                {
                    //ok, alles ist korrekt, wir machen nix
                }
                else
                {
                    //ok, wenn condition da, dann wert setzen, ansonsten Conditionattribute erzeugen
                    if (nodeImport.Attributes["Condition"] != null)
                    {
                        nodeImport.Attributes["Condition"].Value = conditionString;
                    }
                    else
                    {
                        XmlAttribute condiAttribute = csprojfile.CreateAttribute("Condition"); //, "http://schemas.microsoft.com/developer/msbuild/2003");
                        condiAttribute.Value = conditionString;
                        nodeImport.Attributes.Append(condiAttribute);
                    }
                }
            }
        }

        private void AddAfterBuildNode(XmlDocument csprojfile, XmlNode nodeProject)
        {
            Helpers.LogMessage((DTE)this.GetService(typeof(DTE)), this, "Adding new Target node 'AfterBuild'");
            XmlElement afterBuildNode = csprojfile.CreateElement("Target", "http://schemas.microsoft.com/developer/msbuild/2003");
            nodeProject.AppendChild(afterBuildNode);
            XmlAttribute nameAttribute2 = csprojfile.CreateAttribute("Name"); //, "http://schemas.microsoft.com/developer/msbuild/2003");
            nameAttribute2.Value = "AfterBuild";
            afterBuildNode.Attributes.Append(nameAttribute2);
            XmlAttribute dependsAttribute2 = csprojfile.CreateAttribute("DependsOnTargets");//, "http://schemas.microsoft.com/developer/msbuild/2003");
            dependsAttribute2.Value = "$(AfterBuildDependsOn)";
            afterBuildNode.Attributes.Append(dependsAttribute2);
        }

        private void AddBeforeBuildNode(XmlDocument csprojfile, XmlNode nodeProject)
        {
            Helpers.LogMessage((DTE)this.GetService(typeof(DTE)), this, "Adding new Target node 'BeforeBuild'");
            XmlElement beforeBuildNode = csprojfile.CreateElement("Target", "http://schemas.microsoft.com/developer/msbuild/2003");
            nodeProject.AppendChild(beforeBuildNode);
            XmlAttribute nameAttribute = csprojfile.CreateAttribute("Name");//, "http://schemas.microsoft.com/developer/msbuild/2003");
            nameAttribute.Value = "BeforeBuild";
            beforeBuildNode.Attributes.Append(nameAttribute);
            XmlAttribute dependsAttribute = csprojfile.CreateAttribute("DependsOnTargets");//, "http://schemas.microsoft.com/developer/msbuild/2003");
            dependsAttribute.Value = "$(BeforeBuildDependsOn)";
            beforeBuildNode.Attributes.Append(dependsAttribute);
        }



        /// <summary>
        /// Removes the previously added reference, if it was created
        /// </summary>
        public override void Undo()
        {
        }
    }
}